[watchOS 3] Watch Connectivity のデータ受け取りをバックグラウンドで行う
はじめに
こんにちは。モバイルアプリサービス部の平屋です。
前回の記事「[watchOS 3] WKSnapshotRefreshBackgroundTask を使用して watchOS アプリの UI 更新する」に引き続き、watchOS 3.0 で追加されたバックグラウンド実行用のクラスについての情報を紹介していきます。
本記事では watchOS 3.0 で追加された WKWatchConnectivityRefreshBackgroundTask
クラスの使用方法を紹介します。
このクラスは watchOS 2 で追加された機能「Watch Connectivity」に関連します。
検証環境
- Xcode Version 8.1
- iPhone 7, iOS 10.1
- Apple Watch, watchOS 3.1
目次
- Watch Connectivity とは
- [1] セッションをアクティブにする
- [2] データを送信する
- [3] タスクを受け取る
- [4] データを受け取る
- [5] タスクの完了を OS に伝える
- 動作結果
- さいごに
Watch Connectivity とは
Watch Connectivity は watchOS アプリと iOS アプリ間で情報をやりとりするための機能であり、watchOS 2 で追加されました。
Watch Connectivity には「Background transfers」と「Interactive messaging」の 2 つのカテゴリが存在します。データのやりとりをリアルタイムで行いたい場合は Interactive messaging を、そうでない場合は Background transfers を使用します。
- Background transfers
- Application context
- User info transfer
- Complication data transfer
- File transfer
- Interactive messaging
watchOS 3.0 での変更点
watchOS 3.0 では Background transfers のためのクラス WKWatchConnectivityRefreshBackgroundTask
が追加されました。このクラスは、データの受け取りをバックグラウンドで行う場合に使用するクラスです。
本記事では Background transfers の User info transfer を使用して iOS アプリから watchOS アプリへデータを転送し、watchOS アプリ側でのデータの受け取りをバックグラウンドで行う手順を解説していきます。
[1] セッションをアクティブにする
まずは、iOS アプリと watchOS アプリ間のセッションをアクティブにします。
セッションの delegate 設定とアクティベーション
任意のタイミングで、WCSession
の delegate
プロパティにオブジェクトを割り当て、activate()
メソッドを呼びます。
iOS
class ViewController: UITableViewController { // ... override func viewDidLoad() { super.viewDidLoad() if (WCSession.isSupported()) { // デバイスが Watch Connectivity に対応してる場合 let session = WCSession.default() session.delegate = self session.activate() } } // ... }
watchOS
class InterfaceController: WKInterfaceController { // ... override func awake(withContext context: Any?) { super.awake(withContext: context) if (WCSession.isSupported()) { // デバイスが Watch Connectivity に対応してる場合 let session = WCSession.default() session.delegate = self session.activate() } } // ... }
実装必須の delegate メソッドを実装
WCSessionDelegate
のメソッドのうち、実装必須のメソッドを実装します。
セッションのアクティベーションが完了すると、session(_:activationDidCompleteWith:error:)
メソッドが呼ばれます。
iOS
以下のメソッドの実装が必須です。
session(_:activationDidCompleteWith:error:)
sessionDidBecomeInactive(_:)
sessionDidDeactivate(_:)
watchOS
以下のメソッドの実装が必須です。
session(_:activationDidCompleteWith:error:)
[2] データを送信する
User info transfer を使用して iOS から watchOS へデータを送信します。
WCSession
の transferUserInfo(_:)
メソッドを使用すると、WKWatchConnectivityRefreshBackgroundTask
の生成がトリガーされます。
iOS
// MARK: - UITableViewDelegate extension ViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // ここでは、色を指定する文字列を送信する let color = self.rows[indexPath.row] WCSession.default().transferUserInfo(["color" : color]) } }
[3] タスクを受け取る
手順 2 によって生成された WKWatchConnectivityRefreshBackgroundTask
は WKExtensionDelegate
の handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)
メソッドで取得可能です。このメソッドは、前回、前々回の記事でも使用したメソッドです。
ここで取得できる WKWatchConnectivityRefreshBackgroundTask
は、データの転送処理が終わるまで、参照を保持しておきます。
watchOS
class InterfaceController: WKInterfaceController { // ... fileprivate var wcBackgroundTasks: [WKWatchConnectivityRefreshBackgroundTask] = [] // ... } extension InterfaceController: WKExtensionDelegate { func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) { for task in backgroundTasks { if let task = task as? WKWatchConnectivityRefreshBackgroundTask { // タスクが完了するまで、WKWatchConnectivityRefreshBackgroundTask への参照を保持しておく // タスクの保持は開発者の責務 (WWDC 16 のセッション 218 で言及) self.wcBackgroundTasks.append(task) } } } }
[4] データを受け取る
受信したデータは WCSessionDelegate
の session(_:didReceiveUserInfo:)
メソッドで取得できます。
watchOS
extension InterfaceController: WCSessionDelegate { func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) { // データを取り出す guard let color = userInfo["color"] as? String else { return } // watchOS アプリにデータを反映する処理 DispatchQueue.main.async(execute: { () in self.label.setText("■■■ \(color) ■■■") self.label.setTextColor(self.colorMapping[color]) }) } // ... }
[5] タスクの完了を OS に伝える
awake(withContext:)
メソッド内などで WCSession
の hasContentPending
プロパティの変更の監視を開始しておきます。
データの転送が終わると hasContentPending
プロパティの値が false
になるので、そのタイミングでタスクを完了させます。
watchOS
class InterfaceController: WKInterfaceController { // ... override func awake(withContext context: Any?) { super.awake(withContext: context) // ... WCSession.default().addObserver(self, forKeyPath: "hasContentPending", options: [], context: nil) } // ... override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { DispatchQueue.main.async { let session = WCSession.default() // データの転送が終わると、hasContentPending は false になる if session.activationState == .activated && !session.hasContentPending { self.wcBackgroundTasks.forEach { $0.setTaskCompleted() } self.wcBackgroundTasks.removeAll() } } } }
実装の解説は以上になります。
動作結果
watchOS アプリをスリープさせて 2 分後に iOS から watchOS へデータを送信してみたところ、その 3 分後ぐらいに watchOS 側で WKWatchConnectivityRefreshBackgroundTask
を取得することができました。
さいごに
本記事では watchOS 3.0 で追加された新クラス WKWatchConnectivityRefreshBackgroundTask
の使用方法を紹介しました。
今回は iOS アプリ -> watchOS アプリ方向の User info transfer だけを試しましたが、他の転送方法の場合も同様の手順で試すことができるのではないかと思います。